package org.chatty;

import java.io.ByteArrayInputStream;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Control;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.Line;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.SourceDataLine;

/**
 *
 * MUST CALL setup before Player.start!!!
 *
 * @author Brian Hammond
 *
 */
public class Player extends ThreadedByter {

	private SourceDataLine sourceDataLine_;
	private AudioFormat audioFormat_;
	private AudioInputStream audioInputStream_;

	public Player( AudioFormat audioFormat ) throws LineUnavailableException {
		this.setAudioFormat( audioFormat );
		DataLine.Info dataLineInfo = new DataLine.Info( SourceDataLine.class, audioFormat );
		this.setSourceDataLine( ( SourceDataLine ) AudioSystem.getLine( dataLineInfo ) );

		/// ok. probably that getLine stuff crapped out... cuz it is the suck...
		SourceDataLine sourceDataLine = this.autoDetect( audioFormat );
		if ( null == sourceDataLine ) {
			throw new LineUnavailableException();
		}
		this.setSourceDataLine( sourceDataLine );
	}

	public void setup( byte audioData[] ) {
		this.setAudioInputStream( 
			new AudioInputStream(
				new ByteArrayInputStream( audioData )
				, this.getAudioFormat()
				, audioData.length / this.getAudioFormat().getFrameSize()
			)
		);
	}

	public void start_process() throws Exception {
		this.getSourceDataLine().open( this.getAudioFormat() );
		this.getSourceDataLine().start();
	}

	public int process_chunk( byte buffer[] ) throws Exception {
		int count = this.getAudioInputStream().read( buffer, 0, buffer.length );
		if ( -1 != count ) {
			this.getSourceDataLine().write( buffer, 0, count );
		}
		return count;
	}

	public void stop_process() throws Exception {
		this.getSourceDataLine().drain();
		this.getSourceDataLine().close();
	}

	////

	public SourceDataLine getSourceDataLine() {
		return this.sourceDataLine_;
	}

	public void setSourceDataLine( SourceDataLine sourceDataLine ) {
		this.sourceDataLine_ = sourceDataLine;
	}

	public AudioFormat getAudioFormat() {
		return this.audioFormat_;
	}

	public void setAudioFormat( AudioFormat audioFormat ) {
		this.audioFormat_ = audioFormat;
	}

	public AudioInputStream getAudioInputStream() {
		return this.audioInputStream_;
	}

	public void setAudioInputStream( AudioInputStream audioInputStream ) {
		this.audioInputStream_ = audioInputStream;
	}

	//// TODO: move this to "PlaybackSetup"

	private SourceDataLine autoDetect( AudioFormat audioFormat ) {
		return this.autoDetect( AudioSystem.getMixerInfo(), audioFormat );
	}

	private SourceDataLine autoDetect( Mixer.Info mixer_info_s[], AudioFormat audioFormat ) {
		SourceDataLine sourceDataLine = null;
		for ( Mixer.Info mixer_info : mixer_info_s ) {
			sourceDataLine = this.autoDetect( AudioSystem.getMixer( mixer_info ), audioFormat );
			if ( null != sourceDataLine ) break;
		}
		return sourceDataLine;
	}

	private SourceDataLine autoDetect( Mixer mixer, AudioFormat audioFormat ) {
		return this.autoDetect( mixer, mixer.getSourceLineInfo(), audioFormat );
	}

	private SourceDataLine autoDetect( Mixer mixer, Line.Info line_info_s[], AudioFormat audioFormat ) {
		SourceDataLine sourceDataLine = null;
		for ( Line.Info line_info : line_info_s ) {
			if ( line_info instanceof DataLine.Info ) {
				try { 
					sourceDataLine = this.autoDetect( mixer, ( DataLine.Info ) line_info, audioFormat );
				} catch ( Exception e ) {
				}
				if ( null != sourceDataLine ) break;
			}
		}
		return sourceDataLine;
	}

	private SourceDataLine autoDetect( Mixer mixer, DataLine.Info data_line_info, AudioFormat audioFormat ) throws LineUnavailableException {
		return this.autoDetect( 
			( SourceDataLine ) mixer.getLine( data_line_info )
			, audioFormat
		);
	}

	private SourceDataLine autoDetect( SourceDataLine sourceDataLine, AudioFormat audioFormat ) {
		try {
			sourceDataLine.open( audioFormat );
			sourceDataLine.start();

			sourceDataLine.drain();
			sourceDataLine.close();
		} catch( Exception e ) {
			// TODO: plug in some logger
			e.printStackTrace();
			sourceDataLine = null;
		}
		return sourceDataLine;
	}
};
